﻿#define _CRT_SECURE_NO_WARNINGS//忽视部分报错，以便将捕获到的帧写入csv文件
#define HAVE_REMOTE
#define WIN32 //防止引用unix库
#include <string.h>
#include <iostream>
#include <sstream>
#include <pcap.h> // WinPcap内核库
#include <Packet32.h>
#include <ntddndis.h> // Windows DDK驱动程序接口定义
#pragma comment(lib, "Packet")
#pragma comment(lib, "wpcap")
#pragma comment(lib, "WS2_32")

/* 4字节IP地址  */
typedef struct ip_address
{
	u_char byte1;
	u_char byte2;
	u_char byte3;
	u_char byte4;
}ip_address;

/* IPv4 头部 */
typedef struct ip_header
{
	u_char	ver_ihl;		// 版本 (4位) + 互联网头部长度 (4位)
	u_char	tos;			// 服务类型 
	u_short tlen;			// 总长度 
	u_short identification; // 标识
	u_short flags_fo;		// 标志 (3位) + 片偏移 (13位)
	u_char	ttl;			// 生存时间
	u_char	proto;			// 协议
	u_short crc;			// 头部校验和
	ip_address	saddr;		// 源地址
	ip_address	daddr;		// 目的地址
	u_int	op_pad;			// 选项 + 填充
}ip_header;

/* TCP 头部 */
typedef struct tcp_header
{
	u_short sport;	// 源端口号
	u_short dport;	// 目的端口号
	u_int SEQ;		// 序列号
	u_int ACK;		// 确认号
	u_char len;	    // 数据偏移量
	u_char sign; // 保留字段
	u_short win_size; // 窗口大小
	u_short mark; // 标记
	u_short point; // 紧急指针
}tcp_header;

/* 捕获到数据包的回调函数 */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);
int pstr_to_int(char* s1, char* s2);
int port = 21; //ftp端口
int main()
{
	pcap_if_t* alldevs;
	pcap_if_t* d;
	int inum;
	int i = 0;
	pcap_t* adhandle;
	char errbuf[PCAP_ERRBUF_SIZE];
	u_int netmask;
	char packet_filter[] = "tcp";	//设置过滤器为tcp
	struct bpf_program fcode;

	//获取设备列表
	if (pcap_findalldevs(&alldevs, errbuf) == -1)
	{
		fprintf(stderr, "错误: 在获取网络设备列表时出错: %s\n", errbuf);
		exit(1);
	}

	//打印可用网络设备列表
	printf("可用网络设备列表:\n");
	for (d = alldevs; d; d = d->next)
	{
		printf("%d. %s", ++i, d->name);
		if (d->description)
			printf(" (%s)\n", d->description);
		else
			printf(" (无可用描述)\n");
	}

	if (i == 0)
	{
		printf("\n未找到网络设备! 请确保已安装WinPcap.\n");
		return -1;
	}

	printf("请输入要监听的网络设备编号 (1-%d):", i);
	scanf("%d", &inum);

	// 检查用户输入的设备编号是否有效
	if (inum < 1 || inum > i)
	{
		printf("\n设备编号超出范围.\n");
		// 释放设备列表内存
		pcap_freealldevs(alldevs);
		return -1;
	}

	// 跳转到用户选择的网络设备
	for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++);

	// 打开选定的网络设备进行数据包捕获
	if ((adhandle = pcap_open_live(d->name,	// 设备名称
		65536,			// 要捕获的数据包部分长度
		// 65536确保可以在所有MAC上捕获完整的数据包
		1,				// 混杂模式 (非零表示混杂模式)
		1000,			// 读取超时时间(毫秒)
		errbuf			// 错误缓冲区
	)) == NULL)
	{
		fprintf(stderr, "\n无法打开网络设备。%s 不被WinPcap支持\n", d->name);
		// 释放设备列表内存
		pcap_freealldevs(alldevs);
		return -1;
	}

	// 获取网络设备的网络掩码
	if (d->addresses != NULL)
		netmask = ((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
	else
		// 如果设备没有地址，假设为C类网络
		netmask = 0xffffff;

	// 编译数据包过滤器
	if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
	{
		fprintf(stderr, "\n无法编译数据包过滤器。请检查语法。\n");
		// 释放设备列表内存
		pcap_freealldevs(alldevs);
		return -1;
	}

	// 设置数据包过滤器
	if (pcap_setfilter(adhandle, &fcode) < 0)
	{
		fprintf(stderr, "\n设置过滤器时出错。\n");
		// 释放设备列表内存
		pcap_freealldevs(alldevs);
		return -1;
	}

	printf("\n正在监听 %s...\n", d->description);

	// 释放设备列表内存，因为已经不再需要
	pcap_freealldevs(alldevs);

	// 开始捕获数据包
	pcap_loop(adhandle, 0, packet_handler, NULL);

	return 0;
}

/* libpcap为每个传入数据包调用的回调函数 */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
	FILE* file = fopen("data.txt", "a+");
	struct tm* ltime;
	char timestr[16];
	ip_header* ih;
	byte* mac_header;
	tcp_header* th;
	u_int ip_len;
	u_short sport, dport;
	time_t local_tv_sec;
	(VOID)(param);

	// 将时间戳转换为可读格式
	local_tv_sec = header->ts.tv_sec;
	ltime = localtime(&local_tv_sec);
	strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);

	// IP头的位置 (以太网头长度为14字节)
	ih = (ip_header*)(pkt_data + 14);

	// MAC头的位置
	mac_header = (byte*)pkt_data;

	// TCP头的位置 (IP头长度由ver_ihl字段的低4位表示，单位为4字节)
	ip_len = (ih->ver_ihl & 0xf) * 4;
	th = (tcp_header*)((u_char*)ih + ip_len);

	// 从网络字节顺序转换为主机字节顺序
	sport = ntohs(th->sport);
	dport = ntohs(th->dport);

	// 过滤非FTP数据包 (FTP默认端口为21)
	if ((sport != 21 && dport != 21) && (sport != port && dport != port))
		return;

	// 输出数据包基本信息
	printf("时间:%s.%.6d 长度:%d \n", timestr, header->ts.tv_usec, header->len);
	fprintf(file, "时间:%s.%.6d 长度:%d \n", timestr, header->ts.tv_usec, header->len);

	// 输出MAC地址信息
	printf("目的MAC %02x:%02x:%02x:%02x:%02x:%02x \n",
		mac_header[0], mac_header[1], mac_header[2],
		mac_header[3], mac_header[4], mac_header[5]);
	printf("源MAC %02x:%02x:%02x:%02x:%02x:%02x \n",
		mac_header[6], mac_header[7], mac_header[8],
		mac_header[9], mac_header[10], mac_header[11]);

	fprintf(file, "目的MAC %02x:%02x:%02x:%02x:%02x:%02x \n",
		mac_header[0], mac_header[1], mac_header[2],
		mac_header[3], mac_header[4], mac_header[5]);
	fprintf(file, "源MAC %02x:%02x:%02x:%02x:%02x:%02x \n",
		mac_header[6], mac_header[7], mac_header[8],
		mac_header[9], mac_header[10], mac_header[11]);

	// 输出IP地址信息
	printf("目的IP:%d.%d.%d.%d\n",
		ih->daddr.byte1,
		ih->daddr.byte2,
		ih->daddr.byte3,
		ih->daddr.byte4
	);
	printf("源IP:%d.%d.%d.%d\n",
		ih->saddr.byte1,
		ih->saddr.byte2,
		ih->saddr.byte3,
		ih->saddr.byte4
	);

	fprintf(file, "目的IP:%d.%d.%d.%d\n",
		ih->daddr.byte1,
		ih->daddr.byte2,
		ih->daddr.byte3,
		ih->daddr.byte4
	);
	fprintf(file, "源IP:%d.%d.%d.%d\n",
		ih->saddr.byte1,
		ih->saddr.byte2,
		ih->saddr.byte3,
		ih->saddr.byte4
	);

	// 输出TCP端口信息
	printf("TCP.源端口:%d\n", sport);
	printf("TCP.目的端口:%d\n", dport);
	printf("6比特标志位：\n");

	fprintf(file, "TCP.源端口:%d\n", sport);
	fprintf(file, "TCP.目的端口:%d\n", dport);
	fprintf(file, "6比特标志位：\n");

	// 解析TCP标志位
	u_char s = th->sign;
	unsigned short s1 = 0; // URG标志 (紧急指针有效)
	unsigned short s2 = 0; // ACK标志 (确认号有效)
	unsigned short s3 = 0; // PSH标志 (推送数据)
	unsigned short s4 = 0; // RST标志 (重置连接)
	unsigned short s5 = 0; // SYN标志 (同步序列号，用于建立连接)
	unsigned short s6 = 0; // FIN标志 (结束连接)

	if ((s & 32) == 32) // 0010 0000
		s1 = 1;
	if ((s & 16) == 16) // 0001 0000
		s2 = 1;
	if ((s & 8) == 8)   // 0000 1000
		s3 = 1;
	if ((s & 4) == 4)   // 0000 0100
		s4 = 1;
	if ((s & 2) == 2)   // 0000 0010
		s5 = 1;
	if ((s & 1) == 1)   // 0000 0001
		s6 = 1;

	// 输出TCP标志位信息
	printf("URG=%d\tACK=%d\tPSH=%d\tRST=%d\tSYN=%d\tFIN=%d\n", s1, s2, s3, s4, s5, s6);
	fprintf(file, "URG=%d\tACK=%d\tPSH=%d\tRST=%d\tSYN=%d\tFIN=%d\n", s1, s2, s3, s4, s5, s6);

	// 识别TCP连接建立(握手)和断开(挥手)包
	if (s5 == 1) {
		if (s2 == 0) { // SYN=1, ACK=0 (客户端发起连接)
			printf("FTP - TCP三次握手中的第一个包\n");
			fprintf(file, "FTP - TCP三次握手中的第一个包\n");
		}
		else { // SYN=1, ACK=1 (服务器响应连接)
			printf("FTP - TCP三次握手中的第二个包\n");
			fprintf(file, "FTP - TCP三次握手中的第二个包\n");
		}
	}

	if (s6 == 1) { // FIN=1 (连接关闭)
		if (dport == 21) { // 目的端口为21，表示客户端向FTP服务器发送关闭请求
			printf("FTP - TCP四次挥手中的包，客户端向FTP服务器发出\n");
			fprintf(file, "FTP - TCP四次挥手中的包，客户端向FTP服务器发出\n");
		}
		else { // 源端口为21，表示FTP服务器向客户端发送关闭请求
			printf("FTP - TCP四次挥手中的包，FTP服务器向客户端发出\n");
			fprintf(file, "FTP - TCP四次挥手中的包，FTP服务器向客户端发出\n");
		}
	}

	// 判断数据传输方向
	if (dport == 21 || dport == port) {
		printf("客户端--->服务器\n");
		fprintf(file, "客户端--->服务器\n");
	}
	else if (sport == 21 || sport == port) {
		printf("服务器--->客户端\n");
		fprintf(file, "服务器--->客户端\n");
	}

	// 解析TCP数据内容
	char tcp_len = (th->len & 240) >> 4; // 取前4位作为TCP头部长度(单位为4字节)
	if (header->len > 34 + tcp_len * 4) // 34是以太网头+IP头的固定长度
	{
		byte* data_header = (byte*)pkt_data;

		// 输出原始数据
		printf("FTP数据:");
		fprintf(file, "FTP数据:");

		for (int i = 34 + tcp_len * 4; i < header->len; i++) {
			// 只输出可打印ASCII字符
			if (data_header[i] >= 32 && data_header[i] <= 126) {
				printf("%c", data_header[i]);
				fprintf(file, "%c", data_header[i]);
			}
			else {
				printf(".");
				fprintf(file, ".");
			}
		}
		printf("\n");
		fprintf(file, "\n");

		// 解析FTP命令
		if (sport == 21 || dport == 21) {
			char str[100] = { 0 };
			int j = 0;

			// 提取命令关键字(第一个空格前的内容)
			for (int i = 34 + tcp_len * 4; i < header->len && j < 99; i++) {
				if (data_header[i] == ' ' || data_header[i] == '\r' || data_header[i] == '\n') {
					break;
				}
				str[j++] = data_header[i];
			}
			str[j] = '\0';

			// 解析特定FTP命令
			for (int i = 34 + tcp_len * 4; i < header->len; i++) {
				if (data_header[i] == ' ') {
					// 解析USER命令(用户名)
					if (strcmp(str, "USER") == 0) {
						printf("用户名:");
						fprintf(file, "用户名:");

						// 输出用户名(空格后的内容)
						for (i++; i < header->len; i++) {
							if (data_header[i] == '\r' || data_header[i] == '\n')
								break;
							printf("%c", data_header[i]);
							fprintf(file, "%c", data_header[i]);
						}
						printf("\n");
						fprintf(file, "\n");
					}
					// 解析PASS命令(密码)
					else if (strcmp(str, "PASS") == 0) {
						printf("密码:");
						fprintf(file, "密码:");

						// 输出密码(空格后的内容)
						for (i++; i < header->len; i++) {
							if (data_header[i] == '\r' || data_header[i] == '\n')
								break;
							printf("%c", data_header[i]);
							fprintf(file, "%c", data_header[i]);
						}
						printf("\n");
						fprintf(file, "\n");
					}
					// 解析PASV模式响应(被动模式)
					else if (strcmp(str, "227") == 0) {
						int num = 0;	// 用于定位IP地址和端口信息

						// 格式: 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
						// 其中p1和p2组合成端口号: port = p1*256 + p2
						for (; i < header->len; i++) {
							if (num == 4) {
								char s1[10] = { 0 };
								char s2[10] = { 0 };
								int z = 0;

								// 提取p1
								while (i < header->len && data_header[i] != ',') {
									s1[z] = data_header[i];
									z++;
									i++;
								}
								num++;
								i++;

								// 提取p2
								z = 0;
								while (i < header->len && data_header[i] != ')') {
									s2[z] = data_header[i];
									z++;
									i++;
								}

								// 计算被动模式端口
								port = pstr_to_int(s1, s2);
								printf("FTP被动模式端口已更新为: %d\n", port);
								fprintf(file, "FTP被动模式端口已更新为: %d\n", port);
								break;
							}

							if (data_header[i] == ',') {
								num++;
							}
						}
					}
					break;
				}
			}
		}
	}

	printf("\n");
	fprintf(file, "\n");
	fclose(file);
}

/* 计算被动模式下服务器的端口号
   格式: 端口号 = p1*256 + p2
   其中p1和p2是从FTP服务器响应中提取的两个数字 */
int pstr_to_int(char* s1, char* s2) {
	int n1 = atoi(s1);
	int n2 = atoi(s2);
	return 256 * n1 + n2;
}